/*
 *   Copyright 2021 zzh
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */

package com.aduib.boot.common.proxy.interceptor;

import com.aduib.boot.common.exception.CommonException;
import com.aduib.boot.common.proxy.MethodInterceptor;
import com.aduib.boot.common.proxy.Proxy;
import com.aduib.boot.common.util.ClassUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * @description: AbstractMethodInterceptor
 * @author: zzh
 * @date: 2021/10/25 15:54
 */
public class AbstractMethodInterceptor implements MethodInterceptor {

  private final Proxy proxy;

  private final List<String> objectMethods;

  private final List<MethodInterceptor> methodInterceptorList;

  public AbstractMethodInterceptor(Proxy proxy, List<MethodInterceptor> methodInterceptorList) {
    this.proxy = proxy;
    this.objectMethods = initObjectMethod();
    this.methodInterceptorList = methodInterceptorList;
  }

  private List<String> initObjectMethod() {
    ArrayList<String> objectMethods = new ArrayList<>();
    Method[] declaredMethods = Object.class.getDeclaredMethods();
    for (Method declaredMethod : declaredMethods) {
      objectMethods.add(declaredMethod.getName());
    }

    return objectMethods;
  }

  @Override
  public Object invokeMethod(Object proxyObject, Method method, Object[] args) throws Throwable {
    Object sourceObject = proxy.getSourceObject();
    method.setAccessible(true);
    if (objectMethods.contains(method.getName())) {
      if (sourceObject != null) {
        return method.invoke(sourceObject, args);
      } else {
        return null;
      }
    }

    if (sourceObject != null) {
      Object result = null;
      // 开始前回调
      if (invokeBeforeAspect(method, args)) {
        try {
          ClassUtils.setAccessible(method);
          result = ClassUtils.invokeMethod(proxy.getSourceObject(),method,args);
        } catch (CommonException e) {
          // 异常回调（只捕获业务代码导致的异常，而非反射导致的异常）
          invokeAfterExceptionAspect(method, args, e);
        }
      }

      // 结束执行回调
      if (invokeAfterAspect(method, args, result)) {
        return result;
      }
    }
    return null;
  }

  @Override
  public boolean before(Object target, Method method, Object[] args) {
    return false;
  }

  @Override
  public boolean after(Object target, Method method, Object[] args, Object returnVal) {
    return false;
  }

  @Override
  public boolean afterException(Object target, Method method, Object[] args, Throwable e) {
    return false;
  }

  /**
   * 方法执行前
   *
   * @param method
   * @param args
   * @param result
   * @return
   */
  private boolean invokeAfterAspect(Method method, Object[] args, Object result) {
    boolean finishFlag = false;
    for (MethodInterceptor methodInterceptor : methodInterceptorList) {
      if (methodInterceptor.after(proxy.getSourceObject(), method, args, result)) {
        finishFlag = true;
      }
    }
    return finishFlag;
  }

  /**
   * 方法执行出现异常
   *
   * @param method
   * @param args
   * @param e
   * @throws InvocationTargetException
   */
  private void invokeAfterExceptionAspect(Method method, Object[] args, CommonException e)
      throws InvocationTargetException {
    for (MethodInterceptor methodInterceptor : methodInterceptorList) {
      if (methodInterceptor.afterException(proxy.getSourceObject(), method, args, e)) {
        throw e;
      }
    }
  }

  /**
   * 方法执行结束后
   *
   * @param method
   * @param args
   * @return
   */
  private boolean invokeBeforeAspect(Method method, Object[] args) {
    boolean result = false;
    for (MethodInterceptor methodInterceptor : methodInterceptorList) {
      if (methodInterceptor.before(proxy.getSourceObject(), method, args)) {
        result = true;
      }
    }
    return result;
  }
}
